今天總算來到了重頭戲--Routing,也就是負責決定什麼樣的網址要傳到指定的 controller。
Routing 的類別又比 Session 來得更多,而且複雜很多,不過就讓我們一天一天來看吧。
跟 Session 一樣,我們先從 service provider 開始看起。之前有提過,Application 一開始會載入幾個 service provider,其中一個就是 RoutingServiceProvider
:
public function register()
{
$this->registerRouter();
$this->registerUrlGenerator();
$this->registerRedirector();
$this->registerPsrRequest();
$this->registerPsrResponse();
$this->registerResponseFactory();
$this->registerControllerDispatcher();
}
仔細看裡面的實作,會知道註冊幾個類別如下:
'router' => 'Illuminate\Routing\Router'
'routes' => 'Illuminate\Routing\RouteCollection'
'url' => 'Illuminate\Routing\UrlGenerator'
'redirect' => 'Illuminate\Routing\Redirector'
'Psr\Http\Message\ServerRequestInterface' => 'Zend\Diactoros\Request'
'Psr\Http\Message\ResponseInterface' => 'Zend\Diactoros\Response'
'Illuminate\Contracts\Routing\ResponseFactory' => 'Illuminate\Routing\ResponseFactory'
'Illuminate\Routing\Contracts\ControllerDispatcher' => 'Illuminate\Routing\ControllerDispatcher'
除了 PSR request & response 與 ControllerDispatcher 之外,其他都是相互有關係的(為求版本精簡,把 Illuminate\Routing\
省略):
@startuml
UrlGenerator o-- RouteCollection
Redirector o-- UrlGenerator
ResponseFactory o-- Redirector
Router o-- RouteCollection: new instance
@enduml
也因為這樣「食物鏈」的關係,所以會發現 RouteCollection
的初始化是建構 UrlGenerator
時處理的。
搭配上面註冊列表,可以知道這幾個實例,對應到 Laravel 開發者很熟的幾個函式或 Facade:
Route
Facade 對應 Routeraction()
、asset()
、route()
、url()
函式與 URL
Facade 對應 UrlGeneratorback()
、redirect()
函式與 Redirect
Facade 對應 Redirectorresponse()
函式與、Response
Facade 對應 ResponseFactory這些函式都定義在 Foundation 裡的 helpers.php
,而 Facade 則是放在 Support/Facades
裡。
Facade 後面會再說明細節,這裡先知道 Facade 也是對應 Application 註冊的某個特定實例
但從這些註冊的過程,還無法知道哪時候被拿來執行。事實上,執行的時機點,是在 Laravel 的 App\Providers\RouteServiceProvider
裡:
protected $namespace = 'App\Http\Controllers';
public function boot()
{
parent::boot();
}
public function map()
{
Route::prefix('api')
->middleware('api')
->namespace($this->namespace)
->group(base_path('routes/api.php'));
Route::middleware('web')
->namespace($this->namespace)
->group(base_path('routes/web.php'));
}
為說明方便,這是沒有 extract method 的內容。
這裡的程式碼,很明確寫著如何載入 routes 裡面的設定,因此 map()
方法是在什麼時候被呼叫就是關鍵了。實際如何使用 map()
方法的程式,是寫在它的父類別 Illuminate\Foundation\Support\Providers\RouteServiceProvider
,它沒有實作 register()
,而是實作 boot()
:
public function boot()
{
// 設定 namespace 的根
$this->setRootControllerNamespace();
// 看 cache 有沒有 routes 設定
if ($this->app->routesAreCached()) {
// 如果 cache 找得到 routes 設定的話就從 cache 載入
$this->loadCachedRoutes();
} else {
// 找不到就載入
$this->loadRoutes();
// 註冊 app boot 事件
$this->app->booted(function () {
// 執行 RouteCollection 的 refreshNameLookups() 與 refreshActionLookups() 方法
$this->app['router']->getRoutes()->refreshNameLookups();
$this->app['router']->getRoutes()->refreshActionLookups();
});
}
}
loadRoutes()
非常簡單,就是呼叫 map()
。它使用 Application::call()
,會把 map()
方法定義的依賴都注入。因為這裡已經是 boot()
階段了,所有註冊都已經完成,因此不會有找不到依賴的問題。
後面的 refreshNameLookups()
與 refreshActionLookups()
方法看實作是在把裡面的 name mapping 與 action mapping 重新整理,目前的資訊還無法知道為何要執行。
接著繼續看預設的 map()
做的事:
Route::prefix('api')
->middleware('api')
->namespace($this->namespace)
->group(base_path('routes/api.php'));
Route::middleware('web')
->namespace($this->namespace)
->group(base_path('routes/web.php'));
這裡使用了 Facade 來存取 Router 實例,可以想像它其實等同於下面的程式碼:
$this->app->make('router')
->prefix('api')
->middleware('api')
->namespace($this->namespace)
->group(base_path('routes/api.php'));
$this->app->make('router')
->middleware('web')
->namespace($this->namespace)
->group(base_path('routes/web.php'));
最一開始在分析 RouteServiceProvider 時就有發現,router 是對應 Illuminate\Routing\Router
的實例,因此這裡可以知道 Router 實例是最一開始被拿出來使用的。
今天先到此,明天會以 Router 類別為核心來分析 Routing 元件。